﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ionic.Zlib;
using Ionic.Zip;
using System.IO;
using LoLScrape;
using LoLScrape.Inibin;

namespace LoLEdit
{
    class InibinGenerator
    {
        private struct BinHeader
        {
            public Int16 version;
            public Int64 offset;
            public Int64 length;
            public Int16 segmentFlags;
        } //end BinHeader

        /// <summary>
        /// 
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        public static byte[] Generate(Dictionary<UInt32, Object> dictProperties)
        {
            //create the inibin's header
            BinHeader header = CreateHeader(dictProperties);

            //Read each Segment based on the segment flags
            MemoryStream buffer = WriteSegments(header, dictProperties);

            return buffer.ToArray();
        } //end scrapeInibin

        /// <summary>
        /// 
        /// </summary>
        /// <param name="stream"></param>
        private static BinHeader CreateHeader(Dictionary<UInt32, Object> dictProperties)
        {
            BinHeader header = new BinHeader();

            //Retrieve file length
            header.length = 0;

            //Retrieve Version
            header.version = 2;

            //Retrieve Offset
            header.offset = 0;
            
            //Retrieve Segment Flags from the dictionary (key value 0)
            Object intValue = 0;

            bool result = dictProperties.TryGetValue(0, out intValue);

            header.segmentFlags = (Int16)intValue;

            return header;
        } //end readHeader

        /// <summary>
        /// 
        /// </summary>
        /// <param name="header"></param>
        /// <param name="dictProperties"></param>
        /// <returns></returns>
        private static MemoryStream WriteSegments(BinHeader header, Dictionary<UInt32, Object> dictProperties)
        {
            //Instantiate all buffers and binary writers
            //  which will be used to generate the binary file
            MemoryStream [] memKeys    = new MemoryStream [13];
            MemoryStream [] memValues  = new MemoryStream [13];
            MemoryStream    memStrings = new MemoryStream();
            BinaryWriter [] outKeys    = new BinaryWriter [13];
            BinaryWriter [] outValues  = new BinaryWriter [13];
            BinaryWriter    outStrings = new BinaryWriter(memStrings);
            UInt16       [] intCount   = new UInt16 [13];

            //Initialize all buffers and binary writers
            for (int i = 0; i < memKeys.Length; i++)
            {
                memKeys[i] = new MemoryStream();
                outKeys[i] = new BinaryWriter(memKeys[i]);
                memValues[i] = new MemoryStream();
                outValues[i] = new BinaryWriter(memValues[i]);
                intCount[i] = 0;
            } //end for

            //Instantiate and initialize caching variables for special case segments
            // -- Booleans 
            byte byteBooleanCache = 0;
            int intBytePos = 0;
            // -- Strings
            UInt16 intOffsetValue = 0;
            Dictionary<String, UInt16> dictStringLookup = new Dictionary<String, UInt16>();

            //Loop through all of the items in the file dictionary, placing each key/value in their appropriate buffer buckets
            foreach (KeyValuePair<UInt32,Object> item in dictProperties)
            {
                switch(item.Value.GetType().Name)
                {
                    case "Segment01Item":
                        intCount[0]++;
                        outKeys[0].Write((UInt32)item.Key);
                        outValues[0].Write((Int32)((Segment01Item)item.Value).intValue);
                        break;
                    case "Segment02Item":
                        intCount[1]++;
                        outKeys[1].Write((UInt32)item.Key);
                        outValues[1].Write((Single)((Segment02Item)item.Value).fltValue);
                        break;
                    case "Segment03Item":
                        intCount[2]++;
                        outKeys[2].Write((UInt32)item.Key);
                        outValues[2].Write((byte)((Segment03Item)item.Value).intValue);
                        break;
                    case "Segment04Item":
                        intCount[3]++;
                        outKeys[3].Write((UInt32)item.Key);
                        outValues[3].Write((Int16)((Segment04Item)item.Value).intValue);
                        break;
                    case "Segment05Item":
                        intCount[4]++;
                        outKeys[4].Write((UInt32)item.Key);
                        outValues[4].Write((byte)((Segment05Item)item.Value).intValue);
                        break;
                    case "Segment06Item":
                        intCount[5]++;
                        outKeys[5].Write((UInt32)item.Key);

                        //Store the current boolean bit within the byte cache
                        if (((bool)((Segment06Item)item.Value).boolValue) == true)
                            byteBooleanCache |= (byte)(0x1 << intBytePos);
                        
                        //increment the position by one
                        intBytePos++;

                        //Check to see if we've reached the end of the byte cache
                        if (intBytePos > 7)
                        {
                            //Dump the boolean bit storage byte and restart the byte cache.
                            outValues[5].Write((byte)byteBooleanCache);
                            byteBooleanCache = 0;
                            intBytePos = 0;
                        } //end if
                        break;
                    case "Segment07Item":
                        intCount[6]++;
                        outKeys[6].Write((UInt32)item.Key);
                        outValues[6].Write((byte)((Segment07Item)item.Value).B);
                        outValues[6].Write((byte)((Segment07Item)item.Value).G);
                        outValues[6].Write((byte)((Segment07Item)item.Value).R);
                        break;
                    case "Segment08Item":
                        intCount[7]++;
                        outKeys[7].Write((UInt32)item.Key);
                        outValues[7].Write((Single)((Segment08Item)item.Value).B);
                        outValues[7].Write((Single)((Segment08Item)item.Value).G);
                        outValues[7].Write((Single)((Segment08Item)item.Value).R);
                        break;
                    case "Segment09Item":
                        intCount[8]++;
                        outKeys[8].Write((UInt32)item.Key);
                        outValues[8].Write((byte)((Segment09Item)item.Value).X);
                        outValues[8].Write((byte)((Segment09Item)item.Value).Y);
                        break;
                    case "Segment10Item":
                        intCount[9]++;
                        outKeys[9].Write((UInt32)item.Key);
                        outValues[9].Write((Single)((Segment10Item)item.Value).X);
                        outValues[9].Write((Single)((Segment10Item)item.Value).Y);
                        break;
                    case "Segment11Item":
                        intCount[10]++;
                        outKeys[10].Write((UInt32)item.Key);
                        outValues[10].Write((byte)((Segment11Item)item.Value).B);
                        outValues[10].Write((byte)((Segment11Item)item.Value).G);
                        outValues[10].Write((byte)((Segment11Item)item.Value).R);
                        outValues[10].Write((byte)((Segment11Item)item.Value).A);
                        break;
                    case "Segment12Item":
                        intCount[11]++;
                        outKeys[11].Write((UInt32)item.Key);
                        outValues[11].Write((Single)((Segment12Item)item.Value).B);
                        outValues[11].Write((Single)((Segment12Item)item.Value).G);
                        outValues[11].Write((Single)((Segment12Item)item.Value).R);
                        outValues[11].Write((Single)((Segment12Item)item.Value).A);
                        break;
                    case "String":
                        //Attempt to lookup the offset value of the current item's string value if it already exists...
                        // otherwise add it to the output, and add it to the lookup table.
                        intOffsetValue = StringLookupAdd((String)(item.Value), ref dictStringLookup, ref outStrings);

                        intCount[12]++;
                        outKeys[12].Write((UInt32)item.Key);
                        outValues[12].Write((UInt16)intOffsetValue);
                        break;
                } //end switch
            } //end foreach

            //Check to see if we still need to dump any remaining boolean bits
            if (intBytePos > 0)
            {
                //Dump the boolean bit storage byte
                outValues[5].Write((byte)byteBooleanCache);
            } //end if

            //Store the String Data offset size.
            header.offset = memStrings.Length;
            
            //Create the final memory buffer which will store the inibin/troybin binary data...
            MemoryStream buffer = new MemoryStream();
            BinaryWriter output = new BinaryWriter(buffer);

            //Write the Header information, then dump each memory buffer into the final inibin...
            output.Write((byte)header.version);
            output.Write((Int16)header.offset);
            output.Write((Int16)header.segmentFlags);

            //Write all key and value information
            for (int i = 0; i < 13; i++)
            {
                if (intCount[i] != 0)
                {
                    output.Write((UInt16)intCount[i]);
                    memKeys[i].WriteTo(buffer);
                    memValues[i].WriteTo(buffer);
                } //end if
                memKeys[i].Dispose();
                outKeys[i].Dispose();
                memValues[i].Dispose();
                outValues[i].Dispose();
            }//end for

            //Finally add String data section
            memStrings.WriteTo(buffer);
            memStrings.Dispose();
            outStrings.Dispose();

            return buffer;
        } //end WriteSegments

        /// <summary>
        /// 
        /// </summary>
        /// <param name="strString"></param>
        /// <param name="dictStringLookup"></param>
        /// <param name="outStrings"></param>
        /// <returns></returns>
        private static UInt16 StringLookupAdd(String strString, ref Dictionary<String, UInt16> dictStringLookup, ref BinaryWriter outStrings)
        {
            UInt16 intStringOffset = 0;

            //Check to see if this string value has already been written to the buffer.  (example many keys have a value of 0, but we only store 0 once)
            bool boolFoundString = dictStringLookup.TryGetValue(strString, out intStringOffset);

            //If this string has already been stored, then just return the offset position of it.
            if (boolFoundString) return intStringOffset;

            //This string has not yet been written to the buffer, so first record the current position in the buffer...
            intStringOffset = (UInt16)outStrings.BaseStream.Position;

            //Write the string to the buffer, (ensuring to add the mandatory NULL byte afterwards)
            outStrings.Write(strString.ToCharArray());
            //outStrings.BaseStream.Position -= 1;
            outStrings.Write((byte)'\0');

            //Add the string and offset to the lookup table.
            dictStringLookup.Add(strString, intStringOffset);

            //Return the offset value of the string
            return intStringOffset;
        } //end StringLookupAdd
     

    } //end InibinGenerator

} //end LoLEdit
